# Copyright 2007 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import logging
import os
import shutil
import subprocess
import re
import sys
import time
import unittest

from gvn import platform
from errno import ENOENT

import svn.wc
from svn.core import SVN_AUTH_PARAM_DEFAULT_PASSWORD, svn_auth_set_parameter
from svn.core import svn_path_internal_style

import testing.httpd
import testing.svnserve

from testing import repository

from testing import tmpdir
from gvn import svncmd
from gvn import config
from gvn import svnauth

NON_ASCII_WORD = u'S\xf8ren'

DEFAULT_CHECKOUT_PATH = '/public/projects'

_TMP_REPO = None
_TMP_REPO_URL = None
_homebase = None
_username = None
_password = None

def RaSvn(host, port):
  global _MakeURL
  def _MakeURL(path):
    return 'svn://%s:%d/%s' % (host, port, os.path.basename(path))

def RaDav(host, port):
  global _MakeURL
  def _MakeURL(path):
    return 'http://%s:%d/%s' % (host, port, os.path.basename(path))

def RaLocal():
  global _MakeURL
  def _MakeURL(path):
    if sys.platform == 'win32':
      # _TMP_REPO now holds a "Unix-style" absolute Windows path
      # (meaning it has / instead of \\), e.g. 'C:/blah/foo'.  But
      # file URLs for Windows paths are of the form
      # 'file:///C:/blah/foo', so we need to prepend a third slash.
      return 'file:///' + path
    else:
      return 'file://' + path

# Default to ra-local.
RaLocal()

def NewTmpRepository():
  # XXX hack; we need a sane way to have one repo per test
  global _TMP_REPO, _TMP_REPO_URL, _homebase
  os.chdir(_homebase)

  if _TMP_REPO is not None:
    platform.rmtree(_TMP_REPO)

  _TMP_REPO = NewRepository()
  _TMP_REPO_escaped = _TMP_REPO.replace(' ', '%20')
  _TMP_REPO_URL = _MakeURL(_TMP_REPO_escaped)

  return _TMP_REPO

def TmpRepositoryURL():
  return _TMP_REPO_URL

def GetConfig():
  svncmd.SetEffectiveUsername(UnittestUser())

  cfg = config.Config()
  cfg._data['default_username'] = UnittestUser()
  cfg.project_dirs = [os.path.join(_homebase, 'home', '.gvn', 'projects')]
  cfg.SetSvnConfig(os.path.join(_homebase, 'home', '.subversion'), pool=None)
  cfg.StoreAuthCreds = cfg.StorePasswords = False
  return cfg

def GetAuthBaton(cfg, pool, repo=None):
  auth_baton = svnauth.GetAuthBaton(UnittestUser(), False, cfg, pool)
  svn_auth_set_parameter(auth_baton[-1],
                         SVN_AUTH_PARAM_DEFAULT_PASSWORD, 'rayjandom')
  if repo is not None:
    repo.ra_callbacks.SetAuthBaton(auth_baton)
  return auth_baton


def UnittestUser():
  """A random username which can be used as an effective svn
  username during unittests (so that calls to the os.getpwuid()
  method aren't required).
  """
  return 'unittest'


def NewEmptyRepository():
  """Create an empty temporary repository.

  A tuple of the full path to the repository and the repository object itself
  is returned. The repository will be deleted upon program exit.
  """
  return repository.CreateEmptyRepository(tmpdir.RandomTmpSubdir())


def NewRepository():
  """Create a temporary repository.

  The fullpath to the repository is returned (with / instead of \\, if we're
  on Windows). The repository will be deleted upon program exit.
  """
  return repository.CreateRepository(tmpdir.RandomTmpSubdir())


def CopyTestData(src, dst):
  # Copied from shutil "copytree()" example.
  names = os.listdir(src)

  # The outermost directory already exists.
  if not os.path.exists(dst):
    os.mkdir(dst)

  for name in names:
    if name == '.svn':
      continue

    srcname = os.path.join(src, name)
    dstname = os.path.join(dst, name)

    if os.path.isdir(srcname):
      CopyTestData(srcname, dstname)
    else:
      shutil.copy2(srcname, dstname)

def NewWorkingCopy(rdir=None, checkout_path=None):
  global _username, _password

  if rdir is None:
    rdir = TmpRepositoryURL()

  if checkout_path is None:
    checkout_path = DEFAULT_CHECKOUT_PATH

  wcdir = tmpdir.RandomTmpSubdir()

  here = os.getcwd()
  os.chdir(wcdir)
  checkout_args = ['checkout', "%s/%s" % (rdir, checkout_path.lstrip('/')),
                   '.']
  if _username is not None:
    checkout_args.insert(1, '--username')
    checkout_args.insert(2, _username)
    checkout_args.insert(3, '--password')
    checkout_args.insert(4, _password)
  (rcode, stdout, stderr) = svncmd.CaptureSvn(checkout_args)

  # /tmp is a symlink to /var/tmp/tmp on grhat!  Get the correct path.
  wcdir = os.getcwd()

  os.chdir(here)
  if rcode != 0:
    print "rcode: %s" % rcode
    print >>sys.stdout, stdout
    print >>sys.stderr, stderr
    return None

  return svn_path_internal_style(wcdir)


def TestSetUp(dir):
  global _TMP_REPO
  global _TMP_REPO_URL
  global _homebase

  _homebase = os.getcwd()

  tmp_home_dir = os.path.join(dir, 'home')

  if not os.path.exists(tmp_home_dir):
    os.mkdir(tmp_home_dir)
  os.environ['HOME'] = tmp_home_dir

  os.chdir(dir)
  #ListRepository(_TMP_REPO)


## silly things useful for debugging during test writing/fixing
def ListRepository(rdir=None, subdir=None):
  if rdir is None:
    rdir = TmpRepositoryURL()

  if subdir is None:
    subdir = '/'

  svncmd.RunSvn(['ls', '-Rv', "%s/%s" % (rdir, subdir.lstrip('/'))])

def ShellDebug(shell=None):
  if not shell:
    shell = 'bash'
  subprocess.call([shell])

def RunSvn(*args):
  argv = list(args)
  argv.insert(1, '-q')
  assert svncmd.RunSvn(argv) == 0

def sleepf(f, *args, **kwargs):
  now = time.time()
  result = f(*args, **kwargs)
  if time.time() < now + 1:
    time.sleep(1)
  return result

def WCAdd(_wc, path, pool):
  """Wrapper around svn.wc.add2: schedule path for addition."""
  abspath = _wc.AbsolutePath(path)
  svn.wc.add2(abspath,
              svn.wc.adm_probe_retrieve(_wc._adm_access,
                                        os.path.dirname(abspath),
                                        pool),
              None,                     # copyfrom_url
              -1,                       # copyfrom_rev
              None,                     # cancel_func
              None,                     # notify_func
              pool)

def WCDelete(_wc, path, pool):
  """Wrapper around svn.wc.delete2: schedule path for deletion."""
  abspath = _wc.AbsolutePath(path)
  svn.wc.delete2(abspath,
                 svn.wc.adm_probe_retrieve(_wc._adm_access,
                                           os.path.dirname(abspath),
                                           pool),
                 None,                  # cancel_func
                 None,                  # notify_func
                 pool)

def WCCopy(_wc, src, dst, pool):
  """Wrapper around svn.wc.copy2: schedule dst as copy from src."""
  svn.wc.copy2(_wc.AbsolutePath(src),
               svn.wc.adm_probe_retrieve(_wc._adm_access,
                                         os.path.dirname(_wc.AbsolutePath(dst)),
                                         pool),
               os.path.basename(dst),
               None,                    # cancel_func
               None,                    # notify_func
               pool)
